<?php

declare(strict_types=1);

namespace app\common\webdav\fs;

use app\admin\model\StorageChunks;
use app\admin\model\StoragePath;
use app\common\storage\CommonStorage;
use League\Flysystem\FileNotFoundException;
use Sabre\DAV;
use Sabre\DAV\INode;
use Sabre\DAV\MkCol;
use Sabre\DAV\PropPatch;
use think\facade\Log;

/**
 * Directory class.
 *
 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
 * @author Evert Pot (http://evertpot.com/)
 * @license http://sabre.io/license/ Modified BSD License
 */
class Directory extends Node implements
    DAV\ICollection,
    DAV\IMoveTarget,
    DAV\ICopyTarget,
    DAV\IQuota,
    DAV\IExtendedCollection,
    DAV\IMultiGet
{

    /**
     * Creates a new file in the directory.
     *
     * Data will either be supplied as a stream resource, or in certain cases
     * as a string. Keep in mind that you may have to support either.
     *
     * After successful creation of the file, you may choose to return the ETag
     * of the new file here.
     *
     * The returned ETag must be surrounded by double-quotes (The quotes should
     * be part of the actual string).
     *
     * If you cannot accurately determine the ETag, you should not return it.
     * If you don't store the file exactly as-is (you're transforming it
     * somehow) you should also not return an ETag.
     *
     * This means that if a subsequent GET to this new file does not exactly
     * return the same contents of what was submitted here, you are strongly
     * recommended to omit the ETag.
     *
     * @param string          $name Name of the file
     * @param resource|string $data Initial payload
     *
     * @return string|null
     */
    public function createFile($name, $data = null)
    {

        $model_new_path = $this->storageService->createFileByModel($this->currentModelPath, $name, $data);

        return "\"{$model_new_path->chunk_list_md5}\"";
    }

    /**
     * Creates a new subdirectory.
     *
     * @param string $name
     */
    public function createDirectory($name)
    {
        $this->storageService->createDirectoryByModel($this->currentModelPath, $name);
    }

    /**
     * Returns a specific child node, referenced by its name.
     *
     * This method must throw DAV\Exception\NotFound if the node does not
     * exist.
     *
     * @param string $name
     *
     * @throws DAV\Exception\NotFound
     *
     * @return DAV\INode
     */
    public function getChild($name)
    {

        $path = $this->currentModelPath->path . '/' . $name;

        try {
            $model_path = $this->storageService->initModelPath($path);

            if (empty($model_path)) {
                throw new FileNotFoundException($path);
            }
        } catch (FileNotFoundException $th) {
            throw new DAV\Exception\NotFound('File with name ' . $path . ' could not be located');
        }

        if ($model_path['type'] == 'collection') {
            return new Directory($this->modelUser, $model_path);
        } else {
            return new File($this->modelUser, $model_path);
        }
    }


    /**
     * Returns an array with all the child nodes.
     *
     * @return DAV\INode[]
     */
    public function getChildren()
    {
        $nodes = [];

        $list_path = $this->storageService->listContentsByModel($this->currentModelPath);

        foreach ($list_path as  $model_path) {
            if ($model_path['type'] == 'collection') {
                $nodes[] = new Directory($this->modelUser, $model_path);
            } else {
                $nodes[] = new File($this->modelUser, $model_path);
            }
        }

        return $nodes;
    }

    /**
     * Checks if a child exists.
     *
     * @param string $name
     *
     * @return bool
     */
    public function childExists($name)
    {
        $path = $this->currentModelPath->path . '/' . $name;

        $model_path = $this->storageService->initModelPath($path);

        if (empty($model_path)) {
            return false;
        }

        return true;
    }

    /**
     * Deletes all files in this directory, and then itself.
     */
    public function delete()
    {
        $this->storageService->deleteDirectoryByModel($this->currentModelPath);
    }

    /**
     * Moves a node into this collection.
     *
     * It is up to the implementors to:
     *   1. Create the new resource.
     *   2. Remove the old resource.
     *   3. Transfer any properties or other data.
     *
     * Generally you should make very sure that your collection can easily move
     * the move.
     *
     * If you don't, just return false, which will trigger sabre/dav to handle
     * the move itself. If you return true from this function, the assumption
     * is that the move was successful.
     *
     * @param string $targetName new local file/collection name
     * @param string $sourcePath Full path to source node
     * @param INode  $sourceNode Source node itself
     *
     * @return bool
     */
    public function moveInto($targetName, $sourcePath, INode $sourceNode)
    {
        Log::debug('快速移动');
        if ($sourceNode instanceof File || $sourceNode instanceof Directory) {

            $moving_model_path = $sourceNode->getCurrentModelPath();

            $new_path = $this->currentModelPath->path . '/' . $targetName;

            $this->storageService->moveByModel($moving_model_path, $new_path, true, true);

            return true;
        }

        return false;
    }


    /**
     * Copies a node into this collection.
     *
     * It is up to the implementors to:
     *   1. Create the new resource.
     *   2. Copy the data and any properties.
     *
     * If you return true from this function, the assumption
     * is that the copy was successful.
     * If you return false, sabre/dav will handle the copy itself.
     *
     * @param string $targetName new local file/collection name
     * @param string $sourcePath Full path to source node
     * @param INode  $sourceNode Source node itself
     *
     * @return bool
     */
    public function copyInto($targetName, $sourcePath, INode $sourceNode)
    {

        Log::debug('开始快速复制');
        Log::debug($sourceNode);

        if ($sourceNode instanceof Node) {

            $moving_model_path = $sourceNode->getCurrentModelPath();

            $new_path = $this->currentModelPath->path . '/' . $targetName;

            $this->storageService->copyByModel($moving_model_path, $new_path, true);


            return true;
        }

        return false;
    }


    /**
     * Returns the quota information.
     *
     * This method MUST return an array with 2 values, the first being the total used space,
     * the second the available space (in bytes)
     */
    public function getQuotaInfo()
    {
        Log::debug('call quota info');
        $used_space = $this->currentModelPath->storage_quota_used_bytes;

        $total_space = $this->modelUser->total_space;

        if (empty($total_space)) {
            $total_space = 1024 * 1024 * 1024 * 1024 + $used_space;
        }

        $free_space = +$total_space - +$used_space;

        return [
            $used_space,
            $free_space
        ];
    }


    /**
     * Creates a new collection.
     *
     * This method will receive a MkCol object with all the information about
     * the new collection that's being created.
     *
     * The MkCol object contains information about the resourceType of the new
     * collection. If you don't support the specified resourceType, you should
     * throw Exception\InvalidResourceType.
     *
     * The object also contains a list of WebDAV properties for the new
     * collection.
     *
     * You should call the handle() method on this object to specify exactly
     * which properties you are storing. This allows the system to figure out
     * exactly which properties you didn't store, which in turn allows other
     * plugins (such as the propertystorage plugin) to handle storing the
     * property for you.
     *
     * @param string $name
     *
     * @throws Exception\InvalidResourceType
     */
    public function createExtendedCollection($name, MkCol $mkCol)
    {

        if (!$mkCol->hasResourceType('{DAV:}collection')) {
            throw new DAV\Exception\InvalidResourceType('Unknown resourceType for this collection');
        }


        Log::debug($this);

        $this->createDirectory($name);

        $properties = $mkCol->getRemainingValues();
        $mkCol->setRemainingResultCode(201);
        Log::extendCollection($properties);
    }

    /**
     * This method receives a list of paths in it's first argument.
     * It must return an array with Node objects.
     *
     * If any children are not found, you do not have to return them.
     *
     * @param string[] $paths
     *
     * @return array
     */
    public function getMultipleChildren(array $paths)
    {
        Log::debug('getMultipleChildren');

        $list_nodes = [];

        foreach ($paths as $path) {
            $list_nodes[] = $this->getChild($path);
        }

        return $list_nodes;
    }
}
